import os
import tkinter as tk
from tkinter import scrolledtext
from functools import partial
from PIL import Image, ImageTk
from pyzbar import pyzbar
import mss
import math
import threading
import time
import base64
import traceback

stop_event = threading.Event()
selectRectWindow = None
cacheFileNameSuffix = ".wd"
outPath = "out"

_selectAreaButton = None
_cancelSelectAreaButton = None
_testButton = None
_currentPiecesLabel = None
_startButton = None
_stopButton = None
_messageArea = None


def main():
    global _selectAreaButton
    global _cancelSelectAreaButton
    global _testButton
    global _currentPiecesLabel
    global _startButton
    global _stopButton
    global _messageArea
    with mss.mss() as sct:
        monitor = sct.monitors[1]
        width, height = monitor["width"], monitor["height"]
    window = genWindow()
    _selectAreaButton = selectAreaButton(window)
    _cancelSelectAreaButton = cancelSelectAreaButton(window)
    _testButton = testButton(window)
    _currentPiecesLabel = piecesLabel(window)
    setCurrentPieceLabelText(0)
    _startButton = startButton(window)
    _stopButton = stopButton(window)
    _messageArea = messageArea(window)

    window.mainloop()


def messageArea(parent):
    textArea = scrolledtext.ScrolledText(
        parent, wrap=tk.WORD, width=55, height=5)
    textArea["state"] = "disabled"
    textArea.grid(row=1, column=0, columnspan=11)
    return textArea


def showMessage(message, append):
    timeStr = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
    message = timeStr + " -> " + str(message) + os.linesep
    print("### " + message)
    if (append):
        _messageArea["state"] = "normal"
        _messageArea.insert(tk.END, message)
        _messageArea["state"] = "disabled"
    else:
        _messageArea["state"] = "normal"
        _messageArea.delete("1.0", tk.END)
        _messageArea.insert(tk.END, message)
        _messageArea["state"] = "disabled"


def buttonState(selectAreaButtonState, cancelSelectAreaButtonState, testButtonState, startButtonState, stopButtonState):
    _selectAreaButton["state"] = tk.NORMAL if selectAreaButtonState else tk.DISABLED
    _cancelSelectAreaButton["state"] = tk.NORMAL if cancelSelectAreaButtonState else tk.DISABLED
    _testButton["state"] = tk.NORMAL if testButtonState else tk.DISABLED
    _startButton["state"] = tk.NORMAL if startButtonState else tk.DISABLED
    _stopButton["state"] = tk.NORMAL if stopButtonState else tk.DISABLED


def piecesLabel(parent):
    currentPiecesLabel = tk.Label(
        parent, text="",  borderwidth=1, relief="ridge", width=16)
    currentPiecesLabel.grid(row=0, column=5)
    return currentPiecesLabel


def setCurrentPieceLabelText(max=0):
    setLabelText(_currentPiecesLabel, "current: " + str(max))


def setLabelText(label, text=""):
    label.config(text=text)


def stopButton(parent):
    button = tk.Button(parent, text="Stop", state=tk.DISABLED,
                       command=partial(stopButtonClick))
    button.grid(row=0, column=4)
    return button


def stopButtonClick():
    showMessage("stop", True)
    stop_event.set()
    buttonState(True, True, True, True, True)


def startButton(parent):
    button = tk.Button(parent, text="Start", command=partial(startButtonClick))
    button.grid(row=0, column=3)
    return button


def startButtonClick():
    showMessage("start", True)
    stop_event.clear()
    if not selectRectWindow:
        showMessage("select a qr first", True)
        return
    buttonState(False, False, False, False, True)
    thread = threading.Thread(target=parseQr, args=())
    thread.setDaemon(True)
    thread.start()


def parseQr():
    global outPath
    outPath = outPath + "/"
    if (not os.path.exists(outPath)):
        os.makedirs(outPath)
    cacheFile = None
    try:
        x1 = selectRectWindow.winfo_x()
        y1 = selectRectWindow.winfo_y()
        width = selectRectWindow.winfo_width()
        height = selectRectWindow.winfo_height()
        interval = 0.2
        currentIndex = 1
        skipLine = 0
        actualFileName = time.strftime(
            "%Y%m%d%H%M%S", time.localtime(time.time()))
        cacheFileName = actualFileName + cacheFileNameSuffix
        status = 0
        while True:
            if stop_event.isSet():
                return
            time.sleep(interval)
            with mss.mss() as sct:
                qrImg = sct.grab({
                    "left": x1,
                    "top": y1,
                    "width": width,
                    "height": height
                })
                qrImg = Image.frombytes(
                    "RGB", qrImg.size, qrImg.rgb)
                decodedInfo = pyzbar.decode(qrImg)
                if len(decodedInfo) > 0:
                    content = decodedInfo[0].data.decode("utf-8")
                    parsedContent = parseContent(content)
                    if (parsedContent):
                        index, content = parsedContent
                        if (status == 0 and "start" == index):
                            status = 1
                            showMessage("start parsing...", True)
                            startContent = parseContent(content)
                            md5Hex, fileName = startContent
                            showMessage(md5Hex + "/" + fileName, True)
                            cacheFileName = md5Hex + cacheFileNameSuffix
                            cacheFile, count, isNewFile = getCacheFile(
                                cacheFileName)
                            if (not isNewFile):
                                showMessage(
                                    "use cache, will skip [" + str(count) + "], you can change the index when generate", True)
                            currentIndex = count + 1
                            actualFileName = fileName
                        if (status == 0 or "pause" == index):
                            continue
                        if ("end" == index):
                            writeContent(actualFileName, cacheFile)
                            showMessage("end", True)
                            return
                        if (index.isdigit() and currentIndex == int(index)):
                            setCurrentPieceLabelText(currentIndex)
                            currentIndex = currentIndex + 1
                            if (currentIndex > skipLine):
                                cacheFile.write(content + os.linesep)
                        elif (index.isdigit() and currentIndex < int(index)):
                            showMessage(
                                "the speed of parser lower than generator, index: " + str(currentIndex), True)
                            return
    except Exception as e:
        print(traceback.format_exc())
        showMessage("parse qr error-->" + str(e), True)
        return
    finally:
        setCurrentPieceLabelText(0)
        buttonState(True, True, True, True, False)


def writeContent(fileName, cacheFile):
    if not cacheFile:
        print("cacheFile is not exist")
        return
    try:
        finalQrContent = ""
        cacheFile.seek(0, 0)
        line = cacheFile.readline()
        while line:
            line = line.strip(os.linesep)
            finalQrContent = finalQrContent + line
            line = cacheFile.readline()
        byteContent = base64.b64decode(finalQrContent)
        with open(outPath + fileName, "wb") as file:
            file.write(byteContent)
    except Exception as e:
        cacheFile.close()
    else:
        cacheFile.close()
        removeCacheFile(cacheFile.name)


def removeCacheFileLastLine(filePath):
    with open(filePath, "rb+") as file:
        file.seek(0, 2)
        pos = file.tell()
        while (pos > 0 and file.read(1) != b"\n"):
            pos = pos - 1
            file.seek(pos, 0)
        pos = 0 if pos == 0 else pos + 1
        file.seek(pos, 0)
        file.truncate()


def removeCacheFile(filePath):
    if (os.path.exists(filePath)):
        os.remove(filePath)


def getCacheFile(cacheFileName):
    filePath = outPath + cacheFileName
    f = None
    isNewFile = None
    count = 0
    if (os.path.exists(filePath)):
        removeCacheFileLastLine(filePath)
        f = open(filePath, "a+", encoding="utf-8", newline="")
        isNewFile = False
        f.seek(0, 0)
        for line in f:
            count += 1
    else:
        f = open(filePath, "w+", encoding="utf-8", newline="")
        isNewFile = True
    return (f, count, isNewFile)


def parseContent(content):
    index = 0
    for char in content:
        index = index + 1
        if (char == "-"):
            break
    pre = content[0:index-1]
    post = content[index:]
    if (not pre or not post):
        return None
    return (pre, post)


def testButton(parent):
    button = tk.Button(parent, text="Test", command=partial(testButtonClick))
    button.grid(row=0, column=2)
    return button


def testButtonClick():
    print("test")
    if not selectRectWindow:
        showMessage("select a qr first", True)
        return
    x1 = selectRectWindow.winfo_x()
    y1 = selectRectWindow.winfo_y()
    width = selectRectWindow.winfo_width()
    height = selectRectWindow.winfo_height()
    with mss.mss() as sct:
        qrImg = sct.grab({
            "left": x1,
            "top": y1,
            "width": width,
            "height": height
        })
        qrImg = Image.frombytes(
            "RGB", qrImg.size, qrImg.rgb)
        # qrImg.show();
        try:
            decodedInfo = pyzbar.decode(qrImg)
            if len(decodedInfo) == 1:
                content = decodedInfo[0].data.decode("utf-8")
                showMessage("qr content: " + content, True)
            elif len(decodedInfo) == 0:
                showMessage("not found qr", True)
            else:
                showMessage("found > 1 qr", True)
        except Exception as e:
            showMessage("parse qr error", True)


def cancelSelectAreaButton(parent):
    button = tk.Button(parent, text="Cancel Select",
                       command=partial(cancelSelectAreaButtonClick))
    button.grid(row=0, column=1)
    return button


def cancelSelectAreaButtonClick():
    global selectRectWindow
    if not selectRectWindow:
        return
    selectRectWindow.destroy()
    selectRectWindow = None


def selectAreaButton(parent):
    button = tk.Button(parent, text="Select Qr",
                       command=partial(selectAreaButtonClick))
    button.grid(row=0, column=0)
    return button


def selectAreaButtonClick():
    print("select qr")
    cancelSelectAreaButtonClick()
    selectQrWindow = tk.Toplevel()
    selectQrWindow.attributes("-topmost", True)
    selectQrWindow.bind("<Escape>", lambda event: selectQrWindow.destroy())
    selectQrWindow.focus_force()
    selectQrWindow.grid_rowconfigure(1, weight=1)
    selectQrWindow.grid_columnconfigure(1, weight=1)
    selectQrWindow.geometry("400x400+111+111")
    selectQrWindow.attributes("-fullscreen", True)
    # selectQrWindow.resizable(False, False);
    selectQrWindow.configure(bg="black")
    selectQrWindow.attributes("-transparentcolor", "black")
    selectQrWindow.attributes("-alpha", 0.3)
    selectQrWindow.update_idletasks()

    RectangleDrawer(selectQrWindow, onRectangleFinish)

    return selectQrWindow


def onRectangleFinish(selectQrWindow, recXY):
    x1, y1, x2, y2 = recXY
    selectQrWindow.destroy()
    global selectRectWindow
    selectRectWindow = tk.Toplevel()
    selectRectWindow.bind("<Escape>", lambda event: selectRectWindow.destroy())
    selectRectWindow.focus_force()
    geometry = str(math.ceil(x2 - x1)) + "x" + str(math.ceil(y2 - y1)) + \
        "+" + str(math.floor(x1)) + "+" + str(math.floor(y1))
    selectRectWindow.overrideredirect(True)
    selectRectWindow.geometry(geometry)
    selectRectWindow.configure(bg="white")
    selectRectWindow.attributes("-transparentcolor", "white")
    borderWidth = 3
    frame1 = tk.Frame(selectRectWindow, bg="red",
                      padx=borderWidth, pady=borderWidth)
    frame1.pack(fill="both", expand=True)
    frame2 = tk.Frame(frame1, bg="white")
    frame2.pack(fill="both", expand=True)


def createImg(rgba, width, height, text=""):
    img = Image.new("RGBA", (width, height), rgba)
    img_tk = ImageTk.PhotoImage(img)
    return img_tk


def genWindow():
    window = tk.Tk()
    window.title("-")
    window.geometry("577x155-111+111")
    window.iconphoto(False, tk.PhotoImage(file='icon.png'))
    window.resizable(False, False)
    window.attributes("-topmost", True)
    window.bind("<Activate>", windowFocusIn)
    window.bind("<FocusIn>", windowFocusIn)
    return window


def windowFocusIn(event):
    if (selectRectWindow):
        selectRectWindow.attributes("-topmost", True)


class RectangleDrawer:
    def __init__(self, parent, onfinish):
        self.root = parent
        self.onfinish = onfinish
        self.canvas = tk.Canvas(parent, cursor="cross")
        self.canvas.grid(row=0, column=0, rowspan=2,
                         columnspan=2, sticky="nsew")
        self.canvas.bind("<ButtonPress-1>", self.onButtonPress)
        self.canvas.bind("<B1-Motion>", self.onMouseDrag)
        self.canvas.bind("<ButtonRelease-1>", self.onButtonRelease)
        closeButtonX = self.root.winfo_width() - 50
        closeButton = tk.Button(
            self.root, text="X", width=5, bg="white", command=lambda: self.root.destroy())
        closeButton.grid(row=0, column=0, padx=closeButtonX)
        self.startX = None
        self.startY = None
        self.rectId = None
        self.currentRect = None

    def onButtonPress(self, event):
        self.startX = event.x
        self.startY = event.y
        self.rectId = self.canvas.create_rectangle(self.startX, self.startY,
                                                   self.startX, self.startY, outline="red", width=3)

    def onMouseDrag(self, event):
        if (self.rectId):
            self.canvas.coords(self.rectId, self.startX, self.startY,
                               event.x, event.y)

    def onButtonRelease(self, event):
        if (self.rectId):
            coords = self.canvas.coords(self.rectId)
            self.currentRect = {
                "win_x1": coords[0],
                "win_y1": coords[1],
                "win_x2": coords[2],
                "win_y2": coords[3],
            }
            self.onfinish(self.root, self.getRectangleCoords())

    def getRectangleCoords(self):
        if not self.currentRect:
            return None
        win_x1 = self.currentRect["win_x1"]
        win_y1 = self.currentRect["win_y1"]
        win_x2 = self.currentRect["win_x2"]
        win_y2 = self.currentRect["win_y2"]
        win_x = self.root.winfo_rootx()
        win_y = self.root.winfo_rooty()

        screen_x1 = win_x + win_x1
        screen_y1 = win_y + win_y1
        screen_x2 = win_x + win_x2
        screen_y2 = win_y + win_y2
        showMessage(
            f"坐标: ({screen_x1}, {screen_y1}, {screen_x2}, {screen_y2})", True)
        return (screen_x1, screen_y1, screen_x2, screen_y2)


if __name__ == "__main__":
    main()
